Chapter 8 SUBPROGRAMS LET'S LOOK AT A PROCEDURE _________________________________________________________________ Ada was designed to be very modular, and we come =============== to the point where we will study the first and PROCED1.ADA simplest form of modularization, the procedure. =============== If you examine the program named PROCED1.ADA, you will have your first example of a procedure. Some languages call this kind of subprogram a subroutine, others a function, but Ada calls it a procedure. THE PROCEDURE IS LIKE THE MAIN PROGRAM _________________________________________________________________ Beginning with the executable part of the main program, we have two executable statements in lines 14 and 15, each calling a procedure to write a line, if the name has any meaning. As always, the two statements are executed in succession, and each statement is a procedure call to which we would like to transfer control. The procedure itself is defined in lines 7 through 11, and a close inspection will reveal that the structure of the procedure is very similar to the structure for the main program. It is in fact, identical to the main program, and just as we begin executing the main program by mentioning its name, we begin executing the procedure by mentioning its name. The procedure has a declarative part, which is empty in this case, and an executable part which says to display a line of text and return the cursor to the beginning of the next line. When execution reaches the end statement of the procedure, the procedure is complete and control returns to the next successive statement in the calling program. Everything we have said about the main program, which is actually a procedure, is true for the procedure. Thus we can define new types, declare variables and constants, and even define additional procedures in the declarative part of this procedure. Likewise, we can call other procedures, and do assignments and compares in the executable part of the procedure. ORDER OF DECLARATIONS _________________________________________________________________ The procedure must come after all type, constant, and variable declarations. Ada forces you to arrange your program such that all of the smaller declarations must come first, followed by the larger Page 8-1 Chapter 8 - Subprograms ones. This is so that the small declarations don't get lost among the bigger ones. The embedded procedure has all of the flexibility and requirements as those defined for the main procedure. We will give further examples of each of these points in the next few example programs. At this time, compile and run this program, and make sure you understand its operation completely. THREE PROCEDURES _________________________________________________________________ Examine the program named PROCED2.ADA, and you =============== will see three procedures in the declarative PROCED2.ADA part of this program. Jumping ahead to the main =============== program, beginning in line 33, you will see that the program will write a header, write and increment something 7 times, then write an ending statement. Notice how the use of descriptive procedure names resulted in our understanding of what the program will do without looking at the procedures themselves. The names are long, and a bit tedious to type in, but since Ada was designed to be a language that would be written once and read many times, the extra time will pay off when the source code is studied by several persons in the future. WHAT IS A GLOBAL VARIABLE? _________________________________________________________________ The variable named Counter, defined in line 10, is a global variable because it is defined prior to any procedure. It is therefore available for use by any of these procedures and it is, in fact, used by two of them. In line 14, the variable Counter is assigned the value of 1 when the procedure named Write_A_Header is called. Each time Write_And_Increment is called, the current value of Counter is written to the display, along with a message, and the value is incremented. Note carefully that the procedures are not executed in the order they are because of their relative order, but because the main program calls them in that order. The program would work exactly the same way if you moved the first procedure, in its entirety of course, after the procedure Write_An_Ending_Statement. The order of execution is controlled by the order of calls, not the physical order of the procedures. WHY DO PROCEDURES GO IN THE DECLARATIVE PART? _________________________________________________________________ The declarative part of the program is where we define entities for use within the executable part. The procedures are actually definitions of how to do something, so they are right where they belong, but they must come after the smaller declarations. To reiterate a rule stated earlier, the variable must be declared Page 8-2 Chapter 8 - Subprograms prior to the procedure declarations so that it does not get lost among the procedures. Compile and run this program and be sure you understand the output. A PROCEDURE WITH PARAMETERS _________________________________________________________________ Examine the file named PROCED3.ADA and you will =============== find a procedure that requires some data to be PROCED3.ADA supplied to it each time it is called. Three =============== variables are defined as type INTEGER and used in the procedure call in line 28. We will ignore the strange looking constructs in lines 12 through 15 for a couple of paragraphs. The procedure header, beginning in line 18, states that it is expecting three inputs, and each must be of type INTEGER, so we are compatible so far. When the procedure is called, the value of the first variable, which is named Dogs in this case, is taken to the procedure, where the procedure prefers to refer to it by the name Variety1. In like manner, the value of Cats is given to the procedure, and is called Variety2. The variable named Animals is referred to by the name Total in the procedure. The procedure is now ready to do some meaningful work with these variables, but is somewhat limited in what it can do to them because of the mode field of the procedure header. THE MODE OF A PARAMETER _________________________________________________________________ The formal parameter, as it is called in the procedure header, named Variety1, is of mode in which means that it is an input parameter, and therefore cannot be changed within the procedure. Variety1, along with Variety2 for the same reason, is a constant within the procedure, and any attempt to assign a value to it will result in a compile error. The formal parameter named Total, however, is of mode out and can therefore have a new value assigned to it within the procedure. It is illegal to attempt to read this variable within the procedure, because it is of output mode only. If it were defined as being of mode in out, it could be both read from and written to. If no mode is given, the system will use mode in as a default. PARAMETER MODE SELECTION _________________________________________________________________ All variables could be defined as mode in out and there would be no problems, since there would be maximum flexibility, or so it would seem. There is another rule that must be considered, and that rule says that every parameter that is of mode out or in out must be called with a variable as the actual parameter in the calling program. A variable of mode in can use a constant or a variable for the actual parameter in the calling program. We have Page 8-3 Chapter 8 - Subprograms been using the New_Line procedure with a constant in it, such as New_Line(2), and if it had been defined with the formal parameter of mode in out, we would have had to define a variable, assign the value 2 to it, and use the variable in the call. This would have made the procedure a bit more difficult to use, and in fact, somewhat awkward. For this reason, the formal parameter in New_Line was defined using mode in. You should choose the mode of the formal parameters very carefully. The three formal parameters are available for use in the procedure, once they are defined as illustrated, just as if they had been defined in the declarative portion of the procedure. They are not available to any other procedure or the main program, because they are defined locally for the procedure. When used however, their use must be consistent with the defined mode for each. SOME GLOBAL VARIABLES _________________________________________________________________ The three variables declared in line 10 can be referred to in the procedure as well as in the main program. Because it is possible to refer to them in the procedure, they can be changed directly within the procedure. The variable Animals can also be modified when control returns to the main program because it is declared of out mode in the procedure header. This possibility can lead to some rather unusual results. You should spend some time thinking about what this really means. THE PROCEDURE SPECIFICATION _________________________________________________________________ Lines 13, 14, and 15, give an example of a procedure specification. This is an incomplete procedure declaration that you will find useful when you begin writing larger programs. The procedure specification can be included for any procedure, if desired, and it describes the external interface to the procedure without declaring what the procedure actually does. The Pascal programmer will recognize this as being very similar to the forward declaration. More will be said about this topic later. Note that the procedure specification is not required in this case, it is only included as an illustration. Compile and run this program after you understand the simple addition and assignment that is done for purposes of illustration. PROCEDURES CALLING OTHER PROCEDURES _________________________________________________________________ The example program CALLING.ADA contains examples of a procedure calling another procedure, which is perfectly legal if the called procedure is within the scope of the calling procedure. Much more Page 8-4 Chapter 8 - Subprograms will be said about scope later in this tutorial. =============== The only rule that will be mentioned here is CALLING.ADA that the procedure must be defined prior to a =============== call to it. You should have no trouble understanding this program, and when you do, you should compile and execute it. HOW DO WE NEST PROCEDURES? _________________________________________________________________ Examine the program named NESTING.ADA for =============== examples of nested procedures. We mentioned NESTING.ADA earlier that it was possible to embed a =============== procedure within the declarative part of any other procedure. This is illustrated in lines 10 through 21 where the procedure Second_Layer is embedded within the procedure Triple. In addition, the procedure Second_Layer has the procedure Bottom_Layer embedded within its declarative part in lines 12 through 15. Such nesting can continue indefinitely, because there is no limit to the depth of nesting allowed in Ada. VISIBILITY OF PROCEDURES _________________________________________________________________ There is a limit on visibility of procedures. Any procedure is visible, and can therefore be called, if it is within the top level of the declarative part of the calling procedure. Any procedure is also visible if it is prior to, on the same level, and within the same declarative part as the calling point. Finally, any procedure can be called if it is prior to, on the same level, and within the same declarative part as any subprogram within which the calling point in nested. In simpler words, a procedure is visible in three cases. First, if it is within the declarative part of the calling procedure. The second case is if it is a peer (on the same level within a parent subprogram) or thirdly, if it is a peer of any parent. The procedure named Triple can therefore call Second_Layer, but not Bottom_Layer, since it is at a lower level and is not visible. The main program, according to these rules, is only allowed to call Triple, because the other two procedures are nested too deeply for a direct call. Be sure to compile and run this program and study the results. ADA FUNCTIONS _________________________________________________________________ The program named FUNCT.ADA has two examples of Ada functions. A function differs from a procedure in only two ways. A function returns a single value which is used in the place of its call, and all formal parameters of a function must be of type in, with no Page 8-5 Chapter 8 - Subprograms other mode permitted. In the program under =============== consideration, two functions are illustrated, FUNCT.ADA one beginning in line 16, and the other =============== beginning in line 21. Note that each begins with the reserved word function. A FUNCTION SPECIFICATION _________________________________________________________________ In a manner similar to that defined for a procedure we can define a function specification that gives the interface of the function to the outside world. You will find the function specification useful later in your Ada programming efforts. It is similar to the Pascal forward declaration. Note once again, that the function specification is not required, it is only given here as an illustration. The function named Square requires one argument which it prefers to call Val, and which must be of type INTEGER. It returns a value to the main program which will be of type INTEGER because that is the type given between the reserved words return and is in the function body. A function must return a value, and the value is returned by following the reserved word return with the value to be returned. This return must be done in the executable part of the program, as illustrated in line 18. It is an error to fail to execute a return statement and fall through the end of a function. Such a runtime error will be reported by raising the exception Program_Error, which will be explained later in this tutorial. CAN YOU RETURN FROM A PROCEDURE? _________________________________________________________________ It would be well to point out that you can return from a procedure by using the return statement also, but no value can be given since a procedure does not return a value in the same manner as a function. The return statement can be anyplace in the procedure or function and there can be multiple returns if the logic dictates the possibility of returning from several different places. A VALUE IS SUBSTITUTED FOR THE FUNCTION CALL _________________________________________________________________ Examining the executable part of the program, we find that the variable Twelve is initialized to the value of 12, and this variable is used in line 28 as the argument for the function Square. This causes Square to be called, where the value of 12 is squared and the result is returned as 144. It is as if the resulting value of 144 replaces the function call Square(Twelve) in the Put procedure call, and the value of 144 is displayed. Page 8-6 Chapter 8 - Subprograms Continuing on to line 30, the variable Twelve, which is still assigned the value of 12, and the constant 12, are given to the function Sum_Of_Numbers which returns the sum of 24. This value is assigned to the variable named Sum where it is stored for use in line 32. A function can be defined with no input parameters, in which case, the function is called with no parameters. Such a case would be useful for a random number generator where the call could be X := Random; assuming a new random number is returned each time the function is called. Compile and execute this program and study the output generated. ANOTHER NOTE ABOUT THE DERIVED TYPE _________________________________________________________________ As mentioned in chapter 6, a derived type with INTEGER as parent could be declared in line 14, and the derived type would inherit all operations available with INTEGER, including the ability to be used as the formal parameter for the function Square. This means that the function Square could be called with a variable of either of the two types. In order to do this, it is necessary to use a function specification so that the function body will follow the derived type declaration. Ada requires all smaller declarations to be made before subprogram bodies, so they do not get lost among the larger definitions. A FULLER EXAMPLE _________________________________________________________________ Examine the program named ODDSQRE.ADA which is =============== a rather odd program that computes the square of ODDSQRE.ADA an integer type variable, but maintains the sign =============== of the variable. In this program, the odd square of 3 is 9, and the odd square of -3 is -9. Its real purpose is to illustrate several procedures and a function interacting. The main program named OddSqre has a function and a procedure nested within its declarative part, both of which have parameters passed. The nested procedure named Square_And_Keep_Sign has another procedure nested within its declarative part, named Do_A_Negative_Number which calls the function declared at the next higher level. This program is a terrible example of how to solve the problem at hand but is an excellent example of several interacting subprograms, and it would be profitable for you to spend enough time with it to thoroughly understand it. Page 8-7 Chapter 8 - Subprograms COMMENTS ON ODDSQRE.ADA _________________________________________________________________ This program illustrates some of the options that are purely programming taste. The first option is illustrated in line 24, where we could have chosen to use the construct Number_To_Square**2 instead of the simple multiplication. Either form is correct and the one to be used should reflect the nature of the problem at hand. The second option is the fact that three returns were included in lines 39, 42, and 45, when a single return could have been used following the end of the if statement. This was done to illustrate multiple returns in use. In some cases, the logic of the program is much clearer to use several returns instead of only one. More than anything else, it is a matter of personal taste. Be sure to compile and execute this program. OVERLOADING _________________________________________________________________ We have casually mentioned overloading earlier ================ in this tutorial and it is now time to get a OVERLOAD.ADA good example of what overloading is by examining ================ the program named OVERLOAD.ADA. This program includes two functions and two procedures and all four of these subprograms have the same name. The Ada system has the ability to discern which subprogram you wish to use by the types included in the actual parameter list and the type of the return. In line 48, we make a call to a function with a 2, which is an integer type constant, and we assign the returned value to an INTEGER type variable. The system will look for a function named Raise_To_Power with a single integer class formal parameter and an INTEGER type return which it finds in lines 14 through 18, so it executes this function. The actual searching will be done at compile time so the efficiency is not degraded in any way by the overloaded names. If you continue studying this program you will see how the system can find the correct subprogram by comparing types used as formal parameters, and the type returned. Using the same name for several uses is referred to as overloading the subprogram names and is an important concept in the Ada language. OVERLOADING CAN CAUSE YOU PROBLEMS _________________________________________________________________ If we made an error in this example program, by inadvertently omitting the decimal point in line 50, and assigning the result to an integer type variable, the system would simply use the wrong function and generate invalid data for us. An even worse problem could be found if we had a function that used an INTEGER for input and a FLOAT for output, because only one small error could cause Page 8-8 Chapter 8 - Subprograms erroneous results. Because of this, it would be to your advantage to use different subprogram names for different operations, unless using the same names results in clear code. In the case of the text output procedures which we have been using, it makes a lot of sense to overload the output subprograms to avoid confusion. The name Put is used for outputting strings, integers, enumerated types, etc, and we are not confused. Overloading can be an advantage in certain cases but should not be abused just because it is available. Be sure to compile and execute this program. PROGRAMMING EXERCISES _________________________________________________________________ 1. Rewrite TEMPCONV.ADA to do the temperature conversion in a procedure. 2. Rewrite TEMPCONV.ADA to do the temperature conversion in a function. 3. As mentioned in the text, add a function to the program OVERLOAD.ADA that uses an INTEGER for input and returns a FLOAT type result. Remove the decimal point from line 50 to see that the new function is called when we really intended to call the procedure with the floating point number. Page 8-9